package com.typhoon.spring_shiro.service.shiro;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.ExecutionException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.mgt.DefaultFilter;
import org.apache.shiro.web.filter.mgt.FilterChainManager;
import org.apache.shiro.web.filter.mgt.FilterChainResolver;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

/**
 * 自定义ShiroFilterFactory,用于支持 ①session数据的二级缓存，避免同一请求内多次读redis中的session数据 ②针对允许匿名访问的静态资源文件直接放行
 * @author Ternence
 * @date 2016年10月8日
 */
public class CustomShiroFilterFactoryBean extends ShiroFilterFactoryBean {

	private static transient final Logger log = LoggerFactory.getLogger(CustomShiroFilterFactoryBean.class);
	
	/**
	 * 静态资源目录
	 */
	private static List<String> resources ;
	
	private static PathMatcher pathMatcher = new AntPathMatcher();
	
	
	private static final class SpringShiroFilter extends AbstractShiroFilter {
		
		private Map<String, String> filterChainDefinitionMap;
		

		protected SpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
            super();
            if (webSecurityManager == null) {
                throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
            }
            setSecurityManager(webSecurityManager);
            if (resolver != null) {
                setFilterChainResolver(resolver);
            }
        }

		@SuppressWarnings({ "unchecked", "rawtypes" })
		@Override
		protected void doFilterInternal(ServletRequest servletRequest,
				ServletResponse servletResponse,final FilterChain chain)
				throws ServletException, IOException {

			//如果允许匿名访问且为静态资源可直接放行(注：允许匿名≠不需要走shiro代理)
			if (skipPath(servletRequest)) {
				chain.doFilter(servletRequest, servletResponse);
				return;
			}
			

	        Throwable t = null;

	        try {
	            final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
	            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);

	            final Subject subject = createSubject(request, response);

	            //noinspection unchecked
	            subject.execute(new Callable() {
	                public Object call() throws Exception {
	                    updateSessionLastAccessTime(request, response);
	                    executeChain(request, response, chain);
	                    return null;
	                }
	            });
	        } catch (ExecutionException ex) {
	            t = ex.getCause();
	        } catch (Throwable throwable) {
	            t = throwable;
	        } finally {
	        	RequestContext.remove(); //移除reqeust作用域中的缓存数据
	        }

	        if (t != null) {
	            if (t instanceof ServletException) {
	                throw (ServletException) t;
	            }
	            if (t instanceof IOException) {
	                throw (IOException) t;
	            }
	            //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
	            String msg = "Filtered request failed.";
	            throw new ServletException(msg, t);
	        }
	    
		}

		/**
		 * 判断是否可以跳过当前请求路径
		 * @param servletRequest
		 * @return
		 * @date 2016年10月9日
		 * @author Ternence
		 */
		private boolean skipPath(ServletRequest servletRequest) {
			HttpServletRequest req = ((javax.servlet.http.HttpServletRequest)servletRequest);
			String path = req.getRequestURI().replaceFirst(req.getContextPath(), "");
			if (isAnonUri(path) && isResources(path)) {
				return true;
			}
			
			return false;
		}

		private boolean isAnonUri(String path) {
			if (MapUtils.isEmpty(this.filterChainDefinitionMap)) {
				return false;
			}
			
			for (String pattern : filterChainDefinitionMap.keySet()) {
				if(pathMatcher.match(pattern, path) && filterChainDefinitionMap.get(pattern).equalsIgnoreCase(DefaultFilter.anon.name())){
					return true;
				}
			}
			
			return false;
		}
		
		public boolean isResources(String path) {
			if (CollectionUtils.isEmpty(resources)) {
				return false;
			}
			
			for (String pattern : resources) {
				if(pathMatcher.match(pattern, path)){
					return true;
				}
			}
			
			return false;
		}

		public void setFilterChainDefinitionMap(
				Map<String, String> filterChainDefinitionMap) {
			this.filterChainDefinitionMap = filterChainDefinitionMap;
		}
        
        
    }
	
	@SuppressWarnings("rawtypes")
	public Class getObjectType() {
        return SpringShiroFilter.class;
    }
	

	protected AbstractShiroFilter createInstance() throws Exception {

        log.debug("Creating Shiro Filter instance.");

        SecurityManager securityManager = getSecurityManager();
        if (securityManager == null) {
            String msg = "SecurityManager property must be set.";
            throw new BeanInitializationException(msg);
        }

        if (!(securityManager instanceof WebSecurityManager)) {
            String msg = "The security manager does not implement the WebSecurityManager interface.";
            throw new BeanInitializationException(msg);
        }

        FilterChainManager manager = createFilterChainManager();

        //Expose the constructed FilterChainManager by first wrapping it in a
        // FilterChainResolver implementation. The AbstractShiroFilter implementations
        // do not know about FilterChainManagers - only resolvers:
        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);

        //Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
        //FilterChainResolver.  It doesn't matter that the instance is an anonymous inner class
        //here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
        //injection of the SecurityManager and FilterChainResolver:
        SpringShiroFilter shiroFilter = new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
        shiroFilter.setFilterChainDefinitionMap(getFilterChainDefinitionMap());
        return shiroFilter;
    }

	public void setResources(String resources) {
		if (resources != null && !resources.isEmpty()) {			
			this.resources = Arrays.asList(resources.split(","));
		}
	}
	
	
}
